/*
 * This file is part of aion-unique <aion-unique.org>.
 *
 *  aion-unique is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  aion-unique is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with aion-unique.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.aionemu.gameserver.model.gameobjects.instance;

import com.aionemu.gameserver.ai.AIClasss;
import com.aionemu.gameserver.controllers.effect.EffectController;
import com.aionemu.gameserver.controllers.movement.MovementMask;
import com.aionemu.gameserver.model.TaskId;
import com.aionemu.gameserver.model.gameobjects.Creature;
import com.aionemu.gameserver.model.gameobjects.VisibleObject;
import com.aionemu.gameserver.model.gameobjects.interfaces.ISummoned;
import com.aionemu.gameserver.model.gameobjects.knownList.KnownList;
import com.aionemu.gameserver.model.gameobjects.moveData.PlayableMoveData;
import com.aionemu.gameserver.model.gameobjects.player.Player;
import com.aionemu.gameserver.model.gameobjects.stats.SummonGameStats;
import com.aionemu.gameserver.model.gameobjects.stats.SummonLifeStats;
import com.aionemu.gameserver.model.templates.VisibleObjectTemplate;
import com.aionemu.gameserver.model.templates.npcTemplate.CreatureTemplate;
import com.aionemu.gameserver.model.templates.spawn.SpawnTemplate;
import com.aionemu.gameserver.model.templates.stats.SummonStatsTemplate;
import com.aionemu.gameserver.network.aion.serverpackets.SM_ATTACK_STATUS.TYPE;
import com.aionemu.gameserver.network.aion.serverpackets.SM_MOVE;
import com.aionemu.gameserver.network.aion.serverpackets.SM_SUMMON_OWNER_REMOVE;
import com.aionemu.gameserver.network.aion.serverpackets.SM_SUMMON_PANEL_REMOVE;
import com.aionemu.gameserver.network.aion.serverpackets.SM_SUMMON_UPDATE;
import com.aionemu.gameserver.network.aion.serverpackets.SM_SYSTEM_MESSAGE;
import com.aionemu.gameserver.restrictions.RestrictionsManager;
import com.aionemu.gameserver.skillengine.SkillEngine;
import com.aionemu.gameserver.skillengine.model.Skill;
import com.aionemu.gameserver.utils.MathUtil;
import com.aionemu.gameserver.utils.PacketSendUtility;
import com.aionemu.gameserver.utils.ThreadPoolManager;
import com.aionemu.gameserver.utils.gametime.GameTimeManager;
import com.aionemu.gameserver.world.World;

/**
 * @author ATracer
 * 
 */
public class Summon extends Creature implements ISummoned
{

	private Player		master;
	private SummonMode	mode;
	private final byte	level;

	public static enum SummonMode
	{
		ATTACK(0), GUARD(1), REST(2), RELEASE(3);

		private int	id;

		private SummonMode(int id)
		{
			this.id = id;
		}

		/**
		 * @return the id
		 */
		public int getId()
		{
			return id;
		}
	}

	/**
	 * 
	 * @param objId
	 * @param controller
	 * @param spawnTemplate
	 * @param objectTemplate
	 * @param statsTemplate 
	 * @param position
	 */
	public Summon(int objId, SpawnTemplate spawnTemplate, VisibleObjectTemplate objectTemplate, SummonStatsTemplate statsTemplate, byte level)
	{
		super(objId, spawnTemplate);
		this.objectTemplate = objectTemplate;
		this.level = level;
		super.setGameStats(new SummonGameStats(this, statsTemplate));
		super.setLifeStats(new SummonLifeStats(this));
		super.setEffectController(new EffectController(this));
		super.setKnownlist(new KnownList(this));
		this.mode = SummonMode.GUARD;
	}
	
	@Override
	public short getNpcTypeId()
	{
		return 2;
	}

	@Override
	protected final void initializeMoveData()
	{
		this.setMoveData(new PlayableMoveData());
	}

	@Override
	public byte getLevel()
	{
		return this.level;
	}

	/**
	 * @return the owner
	 */
	@Override
	public Player getMaster()
	{
		return master;
	}

	@Override
	public void initializeAi()
	{
		if (ai != null)
		{
			ai.stopAI();
			ai.setTarget(null);
		}

		ai = AIClasss.getInstance().getAIByName("SummonAI", this);
	}

	@Override
	public CreatureTemplate getObjectTemplate()
	{
		return (CreatureTemplate) super.getObjectTemplate();
	}

	public int getNameId()
	{
		return getObjectTemplate().getNameId();
	}

	/**
	 * @return the mode
	 */
	public SummonMode getMode()
	{
		return mode;
	}

	/**
	 * @param mode the mode to set
	 */
	public void setMode(SummonMode mode)
	{
		this.mode = mode;
	}

	@Override
	public void setMaster(Creature master)
	{
		this.master = (Player) master;
	}

	private long	lastAttackMilis	= 0;

	@Override
	public void notSee(VisibleObject object, boolean isOutOfRange)
	{
		super.notSee(object, isOutOfRange);
		if (getMaster() == null)
			return;

		if (object.getObjectId() == getMaster().getObjectId())
		{
			release(UnsummonType.DISTANCE);
		}
	}

	/**
	 * Release summon
	 */
	public void release(final UnsummonType unsummonType)
	{

		if (getMode() == SummonMode.RELEASE)
			return;
		setMode(SummonMode.RELEASE);

		final Player master = getMaster();
		final int summonObjId = getObjectId();

		switch (unsummonType)
		{
			case COMMAND:
				PacketSendUtility.sendPacket(master, SM_SYSTEM_MESSAGE.SUMMON_UNSUMMON(getNameId()));
				PacketSendUtility.sendPacket(master, new SM_SUMMON_UPDATE(this));
				break;
			case DISTANCE:
				PacketSendUtility.sendPacket(master, SM_SYSTEM_MESSAGE.SUMMON_UNSUMMON_BY_TOO_DISTANCE());
				PacketSendUtility.sendPacket(master, new SM_SUMMON_UPDATE(this));
				break;
			case LOGOUT:
			case UNSPECIFIED:
				break;
		}

		ThreadPoolManager.getInstance().schedule(new Runnable()
		{

			@Override
			public void run()
			{
				setMaster(null);
				master.setSummon(null);
				delete();

				switch (unsummonType)
				{
					case COMMAND:
					case DISTANCE:
					case UNSPECIFIED:
						PacketSendUtility.sendPacket(master, SM_SYSTEM_MESSAGE.SUMMON_DISMISSED(getNameId()));
						PacketSendUtility.sendPacket(master, new SM_SUMMON_OWNER_REMOVE(summonObjId));

						// TODO temp till found on retail
						PacketSendUtility.sendPacket(master, new SM_SUMMON_PANEL_REMOVE());
						break;
					case LOGOUT:
						break;
				}
			}
		}, 5000);
	}

	/**
	 * Change to rest mode
	 */
	public void restMode()
	{
		setMode(SummonMode.REST);
		Player master = getMaster();
		PacketSendUtility.sendPacket(master, SM_SYSTEM_MESSAGE.SUMMON_RESTMODE(this.getNameId()));
		PacketSendUtility.sendPacket(master, new SM_SUMMON_UPDATE(this));
		//		checkCurrentHp();
	}

	//WTF?
	//	private void checkCurrentHp()
	//	{
	//		if (!getLifeStats().isFullyRestoredHp())
	//			addTask(TaskId.RESTORE, LifeStatsRestoreService.getInstance().scheduleHpRestoreTask(getLifeStats()));
	//	}

	/**
	 * Change to guard mode
	 */
	public void guardMode()
	{
		setMode(SummonMode.GUARD);
		Player master = getMaster();
		PacketSendUtility.sendPacket(master, SM_SYSTEM_MESSAGE.SUMMON_GUARDMODE(this.getNameId()));
		PacketSendUtility.sendPacket(master, new SM_SUMMON_UPDATE(this));
		//		checkCurrentHp();
	}

	/**
	 * Change to attackMode
	 */
	public void attackMode()
	{
		setMode(SummonMode.ATTACK);
		Player master = getMaster();
		PacketSendUtility.sendPacket(master, SM_SYSTEM_MESSAGE.SUMMON_ATTACKMODE(this.getNameId()));
		PacketSendUtility.sendPacket(master, new SM_SUMMON_UPDATE(this));
		cancelTask(TaskId.RESTORE);
	}

	@Override
	public boolean attackTarget(Creature target, int time)
	{
		Player master = getMaster();

		if (!RestrictionsManager.canAttack(master, target))
			return false;

		int attackSpeed = getGameStats().getAttackSpeed().getCurrentInt();
		long milis = System.currentTimeMillis();
		if (milis - lastAttackMilis < attackSpeed)
		{
			/**
			 * Hack!
			 */
			return false;
		}
		lastAttackMilis = milis;

		return super.attackTarget(target, time);
	}

	@Override
	public void onAttack(Creature creature, int skillId, TYPE type, int damage)
	{
		//temp 
		if (getMode() == SummonMode.RELEASE)
			return;

		super.onAttack(creature, skillId, type, damage);
		PacketSendUtility.sendPacket(getMaster(), new SM_SUMMON_UPDATE(this));
	}

	@Override
	public void onDie(Creature lastAttacker)
	{
		super.onDie(lastAttacker);
		release(UnsummonType.UNSPECIFIED);
	}

	public void useSkill(int skillId, Creature target)
	{
		Skill skill = SkillEngine.getInstance().getSkill(this, skillId, 1, target);
		if (skill != null)
		{
			skill.useSkill();
		}
	}

	public static enum UnsummonType
	{
		LOGOUT, DISTANCE, COMMAND, UNSPECIFIED
	}
	
	/* (non-Javadoc)
	 * @see com.aionemu.gameserver.model.gameobjects.Creature#getMoveData()
	 */
	@Override
	public PlayableMoveData getMoveData()
	{
		return (PlayableMoveData) super.getMoveData();
	}
	
	public void onMove(float x, float y, float z, byte heading, byte movementMask)
	{
		PlayableMoveData moveData = getMoveData();
		moveData.clientX = x;
		moveData.clientY = y;
		moveData.clientZ = z;
		moveData.clientUpdateTime = GameTimeManager.getGameTicks();
	}

	@Override
	public void onStopMove(float x, float y, float z, byte heading)
	{
		super.onStopMove(x, y, z, heading);
		PacketSendUtility.broadcastPacket(this, new SM_MOVE(this));
	}

	@Override
	public void onMoveTo(float x, float y, float z, float x2, float y2, float z2, byte heading, byte movementMask)
	{
		super.onMoveTo(x, y, z, x2, y2, z2, heading, movementMask);
		PacketSendUtility.broadcastPacket(this, new SM_MOVE(this));
	}

	/* (non-Javadoc)
	 * @see com.aionemu.gameserver.model.gameobjects.Creature#updatePosition(int)
	 */
	@Override
	public boolean updatePosition(int gameTicks)
	{
		PlayableMoveData moveData = new PlayableMoveData();
		if ((moveData.movementMask & MovementMask.MOUSE) == 0)
		{
			moveData.xTarget = getX() + moveData.vectorX;
			moveData.yTarget = getY() + moveData.vectorY;
			moveData.zTarget = getZ() + moveData.vectorZ;
		}
		
		if ((moveData.movementMask & MovementMask.VEHICLE) == MovementMask.VEHICLE)
		{
			World.getInstance().updatePosition(this, moveData.clientX, moveData.clientY, moveData.clientZ, moveData.heading);
			return super.updatePosition(gameTicks);
		}

		if (moveData.clientUpdateTime != 0 && moveData.clientUpdateTime <= gameTicks)
		{
			double clientDif = MathUtil.getDistance(moveData.clientX, moveData.clientY, moveData.clientZ, moveData.xTarget, moveData.yTarget, moveData.zTarget);
			double serverDif = MathUtil.getDistance(getX(), getY(), getZ(), moveData.xTarget, moveData.yTarget, moveData.zTarget);
			double dif = clientDif - serverDif;

			if (dif > 0)
				World.getInstance().updatePosition(this, moveData.clientX, moveData.clientY, moveData.clientZ, moveData.heading);
		}
		return super.updatePosition(gameTicks);
	}
}
